package com.btl.component.crypto;

import com.btl.boot.core.util.JsonUtils;
import com.btl.boot.exception.SysException;
import com.btl.component.crypto.constant.SecVersionEnum;
import com.btl.component.crypto.dto.ByteArrayHttpInputMessage;
import com.btl.component.crypto.dto.CryptoDto;
import com.btl.component.crypto.dto.CryptoHeader;
import com.btl.component.crypto.exception.DecryptException;
import com.btl.component.crypto.util.AesCryptoUtil;
import com.btl.component.crypto.util.RsaCryptoUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

@Slf4j
public class SecurityCrypt {

    private static final String SEC_REQ_VERSION = "Sec-Req-Version";

    private static SecVersionEnum SEC_CURRENT_VERSION = SecVersionEnum.V1;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static ByteArrayHttpInputMessage decrypt(String encryptedData, HttpHeaders headers) {
        try {
            CryptoDto cryptoDto = objectMapper.readValue(encryptedData, CryptoDto.class);
            String version = cryptoDto.getHeader()
                    .getVersion();
            SecVersionEnum secVersionEnum = SecVersionEnum.valueOf(version);
            String header = objectMapper.writeValueAsString(cryptoDto.getHeader());
            boolean verify = RsaCryptoUtil.verify(version, secVersionEnum.getClientKeyStorePath(), secVersionEnum.getClientKeyStorePassword(), header + cryptoDto.getPayload(), cryptoDto.getSignature());
            if (verify) {
                ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if (Objects.nonNull(servletRequestAttributes)) {
                    servletRequestAttributes.setAttribute(SEC_REQ_VERSION, secVersionEnum, RequestAttributes.SCOPE_REQUEST);
                }
                String aesKey = RsaCryptoUtil.decrypt(version, secVersionEnum.getServerKeyStorePath(), secVersionEnum.getServerKeyStorePassword(), cryptoDto.getHeader()
                        .getKey());
                String decryptPayload = AesCryptoUtil.decrypt(cryptoDto.getPayload(), aesKey);
                return new ByteArrayHttpInputMessage(decryptPayload.getBytes(StandardCharsets.UTF_8), headers);
            } else {
                throw new SysException("签名异常");
            }
        } catch (SysException e) {
            throw new DecryptException(e.getMessage());
        } catch (Exception e) {
            throw new DecryptException("解密异常");
        }
    }

    public static Object encrypt(Object body) {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(servletRequestAttributes)) {
            Object attribute = servletRequestAttributes.getAttribute(SEC_REQ_VERSION, RequestAttributes.SCOPE_REQUEST);
            if (Objects.nonNull(attribute) && attribute instanceof SecVersionEnum secVersionEnum) {
                SEC_CURRENT_VERSION = secVersionEnum;
            }
        }
        CryptoDto secReq = new CryptoDto();
        try {
            String key = AesCryptoUtil.generateKey();
            String dataStr = JsonUtils.toJsonString(body);
            if (StringUtils.hasText(dataStr)) {
                String encryptData = AesCryptoUtil.encrypt(dataStr, key);
                String encryptKey = RsaCryptoUtil.encrypt(SEC_CURRENT_VERSION.getClientName(), SEC_CURRENT_VERSION.getClientKeyStorePath(), SEC_CURRENT_VERSION.getClientKeyStorePassword(), key);
                CryptoHeader cryptoHeader = new CryptoHeader();
                cryptoHeader.setVersion(SEC_CURRENT_VERSION.getServerName());
                cryptoHeader.setKey(encryptKey);
                cryptoHeader.setTimestamp(System.currentTimeMillis());
                String header = objectMapper.writeValueAsString(cryptoHeader);
                String sign = RsaCryptoUtil.sign(SEC_CURRENT_VERSION.getServerName(), SEC_CURRENT_VERSION.getServerKeyStorePath(), SEC_CURRENT_VERSION.getServerKeyStorePassword(), header + encryptData);
                secReq.setHeader(cryptoHeader);
                secReq.setPayload(encryptData);
                secReq.setSignature(sign);
            }
        } catch (SysException e) {
            throw new DecryptException(e.getMessage(), e);
        } catch (Exception e) {
            log.error("加密异常", e);
            throw new DecryptException("加密异常");
        }
        return secReq;
    }


}
